<?php declare(strict_types=1);

/**
 * @author      guosir
 * @copyright   2023-2025 owner
 **/

namespace Srv\Apps\Apis\Account\Ctl;

use Srv\Apps\Apis\Common\IdxCtl\IdxIp;
use Srv\Apps\Proto\VO\Account\SignInAdmUserVO;
use Srv\Apps\Proto\VO\Account\SignInVerifyCodeVO;
use Srv\Apps\Proto\VO\TokenVO;
use Srv\Libs\Common\CommArray;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommTime;
use Srv\Libs\Frame\AuthData;
use Srv\Libs\Frame\ControllerAbstract;
use Srv\Libs\Plugins\VerifyCode\VerifyCode;
use Srv\Libs\Storage\DataMysql;
use Srv\Libs\Storage\DataRedis;

final class AccountCtl extends ControllerAbstract
{
    private ?DataMysql $DbAuto                  = null;         // DB-Write
    private ?DataRedis $CcAuto                  = null;         // Cache
    public const VERIFY_CODE_EXPIRE_TIME        = 300;          // Cache验证码储时长[秒]
    public const VERIFY_CODE_PREFIX             = 'VC_';        // Cache验证码存储Key前缀
    public const VERIFY_CODE_LENGTH             = 4;            // Cache验证码存储Key前缀
    public const REQ_ID_MUST                    = [4];          // 默认存在的请求 4-当前管理员信息

    /**
     * @return static
     * tag
     */
    final public static function tag():self
    {
        $thisObject                         = self::tagGet();
        if($thisObject instanceof self){
            $thisObject->DbAuto             = self::getDbAuto();
            $thisObject->CcAuto             = ControllerAbstract::getCacheAuto();
        }
        return $thisObject;
    }

    public function isAllowSigIn(int $admUserId):bool
    {
        if($admUserId < 1) return false;
        $isAllowSigIn   = $this->DbAuto->getDataInt('SELECT auir_sign_account FROM '.$this->DbAuto->getTableNameAll('adm_user_record').' WHERE auir_id='.$admUserId.' AND auir_delete=0', 'auir_sign_account') === 1;
        if(!$isAllowSigIn) return false;
        return true; // 已被禁止登入
    }

    /**
     * @param string $account
     * @param string $password
     * @param SignInAdmUserVO $SignInAdmUserVO
     * @param $errCode
     * @return int
     * signInAccountPass
     */
    public function signInAccountPass(string $account, string $password, SignInAdmUserVO &$SignInAdmUserVO, &$errCode):int
    {
        $admUserId      = 0;
        if(strlen($account) < 1 || strlen($account) > 32 || strlen($password) !== 40) {
            $errCode    = 1;    // 参数有误
            return $admUserId;
        }
        $admUserRecord  = $this->DbAuto->getDataOne('SELECT auir_id, auir_account, auir_password, auir_name, auir_avatar, auir_gender, auir_email, auir_mobile, auir_bind, auir_bind_key, auir_sign, auir_sign_account, auir_sign_bind, auir_allow_ips, auir_super, auir_last_sign_num, auir_last_sign_time, auir_last_sign_ip, takr_ids, arlr_ids, auir_create_time, auir_update_time FROM '.$this->DbAuto->getTableNameAll('adm_user_record').' WHERE auir_account=\''.$this->DbAuto->getRealEscapeString($account).'\' AND auir_delete=0');
        $admUserId      = intval($admUserRecord['auir_id']??0);
        if(!isset($admUserRecord['auir_id']) || $admUserId < 1 || strlen($admUserRecord['auir_account']) < 1 ){
            $errCode    = 2;    // 用户不存在
            return $admUserId;
        }
        if(strlen($admUserRecord['auir_password']) < 1){
            $errCode    = 3;    // 密码错误
            return $admUserId;
        }
        $passwordSha    = $this->getAdmUserPwd($password);
        if(strlen($passwordSha) !== 40 || $admUserRecord['auir_password'] !== $passwordSha){
            $errCode    = 3;    // 密码错误
            return $admUserId;
        }
        if(intval($admUserRecord['auir_sign']) !== 1){
            $errCode    = 4;    // 禁止登陆
            return $admUserId;
        }
        $SignInAdmUserVO->setAdmUserId($admUserId);
        $SignInAdmUserVO->setAdmAcc(trim($admUserRecord['auir_account']));
        $SignInAdmUserVO->setName(trim($admUserRecord['auir_name']));
        $SignInAdmUserVO->setAvatar(trim($admUserRecord['auir_avatar']));
        $SignInAdmUserVO->setGender(intval($admUserRecord['auir_gender']));
        return $admUserId;
    }

    /**
     * @param int $admUserId
     * @param string $deviceId
     * @param string $platform
     * @param TokenVO|null $TokenVO
     * @param int $nonceInt
     * @return bool
     * signInUserId
     */
    public function signInUserId(int $admUserId, string $deviceId, string $platform, ?TokenVO &$TokenVO, int &$nonceInt):bool
    {
        if($admUserId < 1 || strlen($deviceId) < 1 || strlen($platform) < 1) return false;
        $authSuper              = false;
        $authStr                = $this->getAdmUserReqString($admUserId, $authSuper);
        $currTime               = CommTime::getTimeStamp(0);
        $currIpIntStr           = IdxIp::tag()->getReqIpNumStr();
        $ddptId                 = 0;    // 当前平台Id
        $dupId                  = 0;    // 当前设备Id
        $tokenData              = AuthData::generateNewToken($admUserId, $authStr, $deviceId, $platform, $authSuper);
        $tokenStr               = $tokenData['tokenStr'];
        $expireTime             = $tokenData['expireTime'];
        $renewTime              = $tokenData['renewTime'];
        $nonceInt               = $tokenData['nonceInt'];
        $tokenKey               = $tokenData['tokenKey'];
        if(strlen($tokenStr) < 32 || $expireTime < $currTime || $renewTime < $currTime || $nonceInt < 1 || strlen($tokenKey) !== 16) return false;
        // 吊销之前的令牌
        $ddptIdList             = [];  // 设备平台类型Id[令牌不互斥]
        $isFail                 = false;
        $tokenKeyOldList        = $this->DbAuto->getData('SELECT utnr_id, utnr_nonce, ddpt_id FROM '.$this->DbAuto->getTableNameAll('adm_user_token').' WHERE auir_id='.$admUserId.' AND utnr_delete=0 AND utnr_expire_time>='.$currTime, $isFail);
        if(!$isFail && CommArray::isForeach($tokenKeyOldList)){
            $utnrIdList             = [];
            foreach($tokenKeyOldList as $val){
                $utnrId             = intval($val['utnr_id']);
                $nonceIntVal        = intval($val['utnr_nonce']);
                $ddptIdVal          = intval($val['ddpt_id']);
                if($utnrId < 1 || $nonceIntVal < 1) continue;
                if($ddptIdVal > 0 && in_array($ddptIdVal, $ddptIdList, true)) continue;
                $utnrIdList[]       = $utnrId;
                AuthData::delCacheTokenKeyData(AuthData::getTokenKeyByNonce($nonceIntVal), $admUserId);
            }
            if(count($utnrIdList) > 0) $this->DbAuto->query('UPDATE '.$this->DbAuto->getTableNameAll('adm_user_token').' SET utnr_delete=1, utnr_update_time='.$currTime.' WHERE utnr_id IN ('.implode(',', $utnrIdList).') AND auir_id='.$admUserId.' AND utnr_delete=0');
        }
        // 写入新令牌
        $sql                    = 'INSERT INTO '.$this->DbAuto->getTableNameAll('adm_user_token').' SET auir_id='.$admUserId.', utnr_nonce='.$nonceInt.', dup_id='.$dupId.', ddpt_id='.$ddptId.', utnr_ip='.$currIpIntStr.', utnr_expire_time='.$expireTime.', utnr_id_renew=0, utnr_renew_time=0, utnr_delete=0, utnr_create_time='.$currTime.', utnr_update_time='.$currTime;
        if($this->DbAuto->getDataId($sql) < 1) return false;
        $TokenVO                = new TokenVO();
        $TokenVO->setTokenStr($tokenStr);
        $TokenVO->setExpireTime($expireTime);
        $TokenVO->setRenewTime($renewTime);
        // 更新最后登入状态
        $this->DbAuto->query('UPDATE '.$this->DbAuto->getTableNameAll('adm_user_record').' SET auir_last_sign_num=auir_last_sign_num+1, auir_last_sign_time='.$currTime.', auir_last_sign_ip='.$currIpIntStr.' WHERE auir_id='.$admUserId.' AND auir_delete=0');
        return true;
    }

    /**
     * @param int $admUserId
     * @param bool $authSuper
     * @return string
     * 获取管理员全部请求权限Key
     */
    public function getAdmUserReqString(int $admUserId, bool &$authSuper): string
    {
        $reqString          = '';
        if($admUserId < 1) return $reqString;
        $isFail             = false;
        $admUserRecord      = $this->DbAuto->getDataOne('SELECT auir_id, takr_ids, arlr_ids, auir_super FROM '.$this->DbAuto->getTableNameAll('adm_user_record').' WHERE auir_id='.$admUserId.' AND auir_delete=0', $isFail);
        if($isFail || !isset($admUserRecord['auir_id'])) return $reqString;
        $reqKeyIds          = CommJson::decodeArray($admUserRecord['takr_ids']);
        $roleIds            = CommJson::decodeArray($admUserRecord['arlr_ids']);
        $reqKeyIds          = array_merge_recursive($reqKeyIds, self::REQ_ID_MUST); // 默认给的权限
        $authSuper          = intval($admUserRecord['auir_super']) === 1;
        if(count($reqKeyIds) < 1 && count($roleIds) < 1) return $reqString;
        // 角色拥有的权限KeyId
        if(count($roleIds) > 0){
            $isFail         = false;
            $sql            = 'SELECT takr_ids FROM '.$this->DbAuto->getTableNameAll('adm_role_record').' WHERE arlr_id IN('.implode(',', $roleIds).') AND arlr_delete=0 AND arlr_status=1';
            $list           = $this->DbAuto->getData($sql, $isFail);
            if(!$isFail && CommArray::isForeach($list)){
                foreach ($list as $item){
                    $takrIds    = array_filter(array_map('intval', CommJson::decodeArray($item['takr_ids'])));
                    $reqKeyIds  = array_keys(array_flip($reqKeyIds)+array_flip($takrIds));  // 合并&去重
                }
            }
        }
        if(count($reqKeyIds) > 0){
            $sql                = 'SELECT takr_id FROM '.$this->DbAuto->getTableNameAll('adm_auth_key_record').' WHERE takr_id IN('.implode(',', $reqKeyIds).')';
            $list               = $this->DbAuto->getData($sql, $isFail);
            if(!$isFail && CommArray::isForeach($list)){
                $takrIdMax      = max(array_filter(array_map('intval', array_column($list, 'takr_id'))));
                if($takrIdMax < 1) return $reqString;
                $reqKeyIdList   = array_fill(0, $takrIdMax, 0); // 填充数组
                foreach ($list as $item){
                    $takrIdItem                 = intval($item['takr_id']);
                    if($takrIdItem < 1) continue;
                    $reqIndex                   = $takrIdItem - 1;
                    $reqKeyIdList[$reqIndex]    = 1;
                }
                if($reqKeyIdList) $reqString    = CommString::binToChar64(implode('', $reqKeyIdList));
            }
        }
        return $reqString;
    }


    /**
     * @param string $password
     * @return string
     * 生成admin用户加密密码
     */
    public function getAdmUserPwd(string $password): string
    {
        if(strlen($password) !== 40) return '';
        $secNum             = 256;  // 环切256次
        $passwordNew        = $password;
        do{
            $passwordNew    = sha1($passwordNew);
            $passwordNew    = substr($passwordNew.$passwordNew, hexdec(substr($passwordNew, 0, 1)), strlen($passwordNew));
        }while(--$secNum > 0);
        return $passwordNew;
    }

    /**
     * @param string $devId
     * @param SignInVerifyCodeVO $SignInVerifyCodeVO
     * @param string $CodeValue
     * @return bool
     * 获取账号登入验证码
     */
    public function getSignInCode(string $devId, SignInVerifyCodeVO $SignInVerifyCodeVO, string &$CodeValue):bool
    {
        if(strlen($devId) < 1) return false;
        $CacheKey           = self::VERIFY_CODE_PREFIX.md5($devId);
        $CacheValue         = CommString::genRandStr(self::VERIFY_CODE_LENGTH);
        $cacheResult        = $this->CcAuto->set($CacheKey, $CacheValue, self::VERIFY_CODE_EXPIRE_TIME);
        if(!$cacheResult) {
            // 设置失败,删除key再设置
            $this->CcAuto->del($CacheKey);
            $cacheResult    = $this->CcAuto->set($CacheKey, $CacheValue, self::VERIFY_CODE_EXPIRE_TIME);
            if(!$cacheResult) return false;
        }
        $CodeValue          = $CacheValue;
        $width              = 120;
        $height             = 40;
        $VerifyCode         = new VerifyCode($width, $height, $CacheValue);
        $SignInVerifyCodeVO->setVerifyCode(base64_encode($VerifyCode->genPng()));
        return true;
    }

    /**
     * @param string $devId
     * @param string $code
     * @return bool
     * checkSignInCode
     */
    public function checkSignInCode(string $devId, string $code):bool
    {
        if(strlen($devId) < 1 || strlen($code) !== self::VERIFY_CODE_LENGTH ) return false;
        $CacheKey       = self::VERIFY_CODE_PREFIX.md5($devId);
        $CacheValue     = strtolower($this->CcAuto->getStr($CacheKey));
        if(strlen($CacheValue) !== self::VERIFY_CODE_LENGTH) return false;
        if($CacheValue !== strtolower($code)) return false;
        $this->CcAuto->del($CacheKey);
        return true;
    }
}